/*
 Copyright (c) 2012 Amit Elbaz

 Permission is hereby granted, free of charge, to any person obtaining a copy
 of this software and associated documentation files (the "Software"), to deal
 in the Software without restriction, including without limitation the rights
 to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 copies of the Software, and to permit persons to whom the Software is
 furnished to do so, subject to the following conditions:

 The above copyright notice and this permission notice shall be included in
 all copies or substantial portions of the Software.

 THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
 SOFTWARE.
 */

#include "aesdp.h"
#include <string.h>
#include <stdlib.h>

static origin_t* origin_init(origin_t* origin) {
	origin->username = NULL;
	origin->session_id = NULL;
	origin->session_version = NULL;
	origin->network_type = NULL;
	origin->address_type = NULL;
	origin->unicast_address = NULL;

	return origin;
}

static void origin_deinit(origin_t* origin) {
	if (origin->username) {
		free(origin->username);
		origin->username = NULL;
	}

	if (origin->session_id) {
		free(origin->session_id);
		origin->session_id = NULL;
	}

	if (origin->session_version) {
		free(origin->session_version);
		origin->session_version = NULL;
	}

	if (origin->network_type) {
		free(origin->network_type);
		origin->network_type = NULL;
	}

	if (origin->address_type) {
		free(origin->address_type);
		origin->address_type = NULL;
	}

	if (origin->unicast_address) {
		free(origin->unicast_address);
		origin->unicast_address = NULL;
	}
}

static bandwidth_node_t* bandwidth_init(bandwidth_node_t* bandwidth) {
	bandwidth->next = NULL;
	bandwidth->bandwidth_type = NULL;
	bandwidth->bandwidth = NULL;

	return bandwidth;
}

static void bandwidth_deinit(bandwidth_node_t* bandwidth) {
	if (bandwidth->next) {
		bandwidth_deinit(bandwidth->next);
		free(bandwidth->next);
		bandwidth->next = NULL;
	}

	if (bandwidth->bandwidth_type) {
		free(bandwidth->bandwidth_type);
		bandwidth->bandwidth_type = NULL;
	}

	if (bandwidth->bandwidth) {
		free(bandwidth->bandwidth);
		bandwidth->bandwidth = NULL;
	}
}

static connection_t* connection_init(connection_t* connection) {
	connection->network_type = NULL;
	connection->address_type = NULL;
	connection->connection_address = NULL;

	return connection;
}

static void connection_deinit(connection_t* connection) {
	if (connection->network_type) {
		free(connection->network_type);
		connection->network_type = NULL;
	}

	if (connection->address_type) {
		free(connection->address_type);
		connection->address_type = NULL;
	}

	if (connection->connection_address) {
		free(connection->connection_address);
		connection->connection_address = NULL;
	}
}

aesdp_t* aesdp_init(aesdp_t* aesdp) {
	aesdp->version = NULL;
	aesdp->origin = NULL;
	aesdp->session_name = NULL;
	aesdp->session_information = NULL;
	aesdp->uri = NULL;
	aesdp->emails = NULL;
	aesdp->phone_numbers = NULL;
	aesdp->connection = NULL;
	aesdp->bandwidth_list = NULL;

	return aesdp;
}

/**
 * returns the string after the delimiter was found.
 * returns null if delimiter was not found.
 */
static char* move_after_delimiter(char* buf_ptr, char delimiter) {
	char* delimiter_in_buf = strchr(buf_ptr, delimiter);
	if (delimiter_in_buf) {
		return ++delimiter_in_buf;
	} else {
		return NULL;
	}
}

/**
 * verify that buf_ptr starts with <expected_type>=
 */
static int verify_record_type(char* buf_ptr, char expected_type) {
	if (buf_ptr[0] == expected_type && buf_ptr[1] == '=')
		return 1;
	else
		return 0;
}

/**
 * puts in ret_buf an allocated string until delimiter is present
 * returns 0 if no delimiter was found in buf
 * if delimiter_found is not NULL, puts in it the delimiter that was found. therefore optional.
 *
 *  * scans each delimiter in order
 */
static int alloc_string_until_delimiter(char* buf_ptr, char* delimiters,
		char** ret_buf, char* ret_delimiter_found) {
	int num_of_delimiters = strlen(delimiters);
	char* buf_start = buf_ptr;
	while (buf_ptr != '\0') {
		int i;
		for (i = 0; i < num_of_delimiters; i++) {
			if (*buf_ptr == delimiters[i]) {
				// for a delimiter, allocating string and returning.
				size_t str_size = buf_ptr - buf_start + 1;
				*ret_buf = (char*) malloc(sizeof(char) * str_size);
				memcpy(*ret_buf, buf_start, str_size - 1); // copy characters.
				(*ret_buf)[str_size - 1] = '\0';

				if (ret_delimiter_found)
					*ret_delimiter_found = delimiters[i];

				return 1;
			}
		}
		buf_ptr++;
	}

	// did not find any delimiters, and reached end of string.
	return 0;
}

static int parse_origin_value(char* buf_ptr, origin_t** origin) {
	*origin = (origin_t*) malloc(sizeof(origin_t));
	origin_init(*origin);
	if (!alloc_string_until_delimiter(buf_ptr, " ", &(*origin)->username, NULL)
			|| !(*origin)->username)
		goto parse_origin_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	if (!alloc_string_until_delimiter(buf_ptr, " ", &(*origin)->session_id,
			NULL) || !(*origin)->session_id)
		goto parse_origin_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	if (!alloc_string_until_delimiter(buf_ptr, " ", &(*origin)->session_version,
			NULL) || !(*origin)->session_version)
		goto parse_origin_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	if (!alloc_string_until_delimiter(buf_ptr, " ", &(*origin)->network_type,
			NULL) || !(*origin)->network_type)
		goto parse_origin_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	if (!alloc_string_until_delimiter(buf_ptr, " ", &(*origin)->address_type,
			NULL) || !(*origin)->address_type)
		goto parse_origin_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	// search until reaching end of record delimiters
	if (!alloc_string_until_delimiter(buf_ptr, "\r\n",
			&(*origin)->unicast_address, NULL) || !(*origin)->unicast_address)
		goto parse_origin_value_fail;

	return 1;

	parse_origin_value_fail: {
		origin_deinit(*origin);
		free(*origin);
		*origin = NULL;
		return 0;
	}
}

static int parse_connection_value(char* buf_ptr, connection_t** connection) {
	*connection = (connection_t*) malloc(sizeof(connection_t));
	connection_init(*connection);
	if (!alloc_string_until_delimiter(buf_ptr, " ",
			&(*connection)->network_type, NULL) || !(*connection)->network_type)
		goto parse_connection_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	if (!alloc_string_until_delimiter(buf_ptr, " ",
			&(*connection)->address_type, NULL) || !(*connection)->address_type)
		goto parse_connection_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ' ');

	// search until reaching end of record delimiters
	if (!alloc_string_until_delimiter(buf_ptr, "\r\n",
			&(*connection)->connection_address, NULL)
			|| !(*connection)->connection_address)
		goto parse_connection_value_fail;

	return 1;

	parse_connection_value_fail: {
		connection_deinit(*connection);
		free(*connection);
		*connection = NULL;
		return 0;
	}
}

static int parse_bandwidth_value(char* buf_ptr,
		bandwidth_node_t** bandwidth) {
	// get last bandwidth
	while (*bandwidth) {
		bandwidth = &((*bandwidth)->next);
	}

	*bandwidth = (bandwidth_node_t*) malloc(sizeof(bandwidth_node_t));
	bandwidth_init(*bandwidth);
	if (!alloc_string_until_delimiter(buf_ptr, ":",
			&(*bandwidth)->bandwidth_type, NULL)
			|| !(*bandwidth)->bandwidth_type)
		goto parse_bandwidth_value_fail;
	buf_ptr = move_after_delimiter(buf_ptr, ':');

	// search until reaching end of record delimiters
	if (!alloc_string_until_delimiter(buf_ptr, "\r\n", &(*bandwidth)->bandwidth,
			NULL) || !(*bandwidth)->bandwidth)
		goto parse_bandwidth_value_fail;

	return 1;

	parse_bandwidth_value_fail: {
		bandwidth_deinit(*bandwidth);
		free(*bandwidth);
		*bandwidth = NULL;
		return 0;
	}
}

static int parse_session_name_value(char* buf_ptr, char** session_name) {
	// search until reaching end of record delimiters
	if (!alloc_string_until_delimiter(buf_ptr, "\r\n", session_name, NULL)
			|| !*session_name)
		return 0;

	// check that session name length is not empty. (RFC 4566 section 5.3)
	// todo: this check is according to RFC, but RFC 4317 SDP samples doesn't seem to enforce it.
	/*if (strlen(*session_name) == 0)
	 return 0;
	 */

	return 1;
}

static int parse_simple_value(char* buf_ptr, char** value) {
	// search until reaching end of record delimiters
	if (!alloc_string_until_delimiter(buf_ptr, "\r\n", value, NULL) || !*value)
		return 0;

	return 1;
}

/**
 * todo: provide more specific error messages, for example: line number.
 */
int aesdp_parse(aesdp_t* aesdp, char* buf, char** err_msg) {
	char* buf_ptr = buf;

	// version: v=0
	if (!verify_record_type(buf_ptr, 'v')
			|| !parse_simple_value(buf_ptr + 2, &aesdp->version)) {
		if (*err_msg)
			*err_msg = "error while parsing version";
		goto aesdp_parse_fail;
	}
	buf_ptr = move_after_delimiter(buf_ptr, '\n');

	// origin: o=jdoe 2890844526 2890842807 IN IP4 10.47.16.5
	if (!verify_record_type(buf_ptr, 'o')
			|| !parse_origin_value(buf_ptr + 2, &aesdp->origin)) {
		if (*err_msg)
			*err_msg = "error while parsing origin";
		goto aesdp_parse_fail;
	}
	buf_ptr = move_after_delimiter(buf_ptr, '\n');

	// session name: s=SDP Seminar
	if (!verify_record_type(buf_ptr, 's')
			|| !parse_session_name_value(buf_ptr + 2, &aesdp->session_name)) {
		if (*err_msg)
			*err_msg = "error while parsing session name";
		goto aesdp_parse_fail;
	}
	buf_ptr = move_after_delimiter(buf_ptr, '\n');

	// session information, optional
	// i=A Seminar on the session description protocol
	if (verify_record_type(buf_ptr, 'i')) {
		if (!parse_simple_value(buf_ptr + 2, &aesdp->session_information)) {
			if (*err_msg)
				*err_msg = "error while parsing session information";
			goto aesdp_parse_fail;
		}

		buf_ptr = move_after_delimiter(buf_ptr, '\n');
	}

	// uri, optional
	// u=http://www.example.com/seminars/sdp.pdf
	if (verify_record_type(buf_ptr, 'u')) {
		if (!parse_simple_value(buf_ptr + 2, &aesdp->uri)) {
			if (*err_msg)
				*err_msg = "error while parsing uri";
			goto aesdp_parse_fail;
		}

		buf_ptr = move_after_delimiter(buf_ptr, '\n');
	}

	// email, optional.
	// can specify multiple emails
	// u=j.doe@example.com (Jane Doe)
	if (verify_record_type(buf_ptr, 'e')) {
		if (!parse_simple_value(buf_ptr + 2, &aesdp->emails)) {
			if (*err_msg)
				*err_msg = "error while parsing emails";
			goto aesdp_parse_fail;
		}

		buf_ptr = move_after_delimiter(buf_ptr, '\n');
	}

	// phone number, optional
	// can specify multiple phone numbers
	// p=+1 617 555-6011
	if (verify_record_type(buf_ptr, 'p')) {
		if (!parse_simple_value(buf_ptr + 2, &aesdp->phone_numbers)) {
			if (*err_msg)
				*err_msg = "error while parsing phone numbers";
			goto aesdp_parse_fail;
		}

		buf_ptr = move_after_delimiter(buf_ptr, '\n');
	}

	// connection, not required if included in all media (TODO - verify this)
	// c=IN IP4 224.2.17.12/127
	if (verify_record_type(buf_ptr, 'c')) {
		if (!parse_connection_value(buf_ptr + 2, &aesdp->connection)) {
			if (*err_msg)
				*err_msg = "error while parsing connection information";
			goto aesdp_parse_fail;
		}

		buf_ptr = move_after_delimiter(buf_ptr, '\n');
	}

	// bandwidth, zero or more.
	// b=X-YZ:128
	while (verify_record_type(buf_ptr, 'b')) {
		if (!parse_bandwidth_value(buf_ptr + 2, &aesdp->bandwidth_list)) {
			if (*err_msg)
				*err_msg = "error while parsing bandwidth";
			goto aesdp_parse_fail;
		}

		buf_ptr = move_after_delimiter(buf_ptr, '\n');
	}

	return 1;

	aesdp_parse_fail: {
		aesdp_deinit(aesdp);
		return 0;
	}
}

void aesdp_deinit(aesdp_t* aesdp) {
	if (aesdp->version) {
		free(aesdp->version);
		aesdp->version = NULL;
	}

	if (aesdp->origin) {
		origin_deinit(aesdp->origin);
		free(aesdp->origin);
		aesdp->origin = NULL;
	}

	if (aesdp->session_name) {
		free(aesdp->session_name);
		aesdp->session_name = NULL;
	}

	if (aesdp->session_information) {
		free(aesdp->session_information);
		aesdp->session_information = NULL;
	}

	if (aesdp->uri) {
		free(aesdp->uri);
		aesdp->uri = NULL;
	}

	if (aesdp->emails) {
		free(aesdp->emails);
		aesdp->emails = NULL;
	}

	if (aesdp->phone_numbers) {
		free(aesdp->phone_numbers);
		aesdp->phone_numbers = NULL;
	}

	if (aesdp->connection) {
		connection_deinit(aesdp->connection);
		free(aesdp->connection);
		aesdp->connection = NULL;
	}

	if (aesdp->bandwidth_list) {
		bandwidth_deinit(aesdp->bandwidth_list);
		free(aesdp->bandwidth_list);
		aesdp->bandwidth_list = NULL;
	}
}
